using System.Runtime.CompilerServices;

namespace Sage.Data.Extensions
{
    /// <summary>
    /// 提供 DateTime 类型的扩展方法，用于格式化和便捷的操作日期和时间。
    /// </summary>
    public static class DateTimeExtensions
    {
        /// <summary>
        /// 根据给定日期与当前日期的关系来格式化日期。
        /// </summary>
        /// <param name="date">要格式化的日期。</param>
        /// <returns>
        /// 返回日期的字符串表示，格式如下：
        /// - 如果日期是今天，仅返回时间（HH:mm）。
        /// - 如果日期在当前月份但不是今天，返回日和时间（dd日 HH:mm）。
        /// - 如果日期在当前年份但不是当前月份，返回月、日和时间（MM月dd日 HH:mm）。
        /// - 如果日期不在当前年份，返回年（最后两位）、月、日和时间（yy年MM月dd日 HH:mm）。
        /// </returns>
        /// <example>
        /// <code>
        /// DateTime now = DateTime.Now;
        /// string formatted = now.ToFriendlyDateString(); // 返回如 "14:30"
        /// 
        /// DateTime yesterday = DateTime.Now.AddDays(-1);
        /// string formatted2 = yesterday.ToFriendlyDateString(); // 返回如 "12日 14:30"
        /// </code>
        /// </example>
        public static string ToFriendlyDateString(this DateTime date)
        {
            DateTime now = DateTime.Now;

            if (date.Date == now.Date)
            {
                // 当天的仅显示时间（时分）
                return date.ToString("HH:mm");
            }
            else if (date.Year == now.Year && date.Month == now.Month)
            {
                // 不是当天，但是在当月的，显示到日+时分
                return date.ToString("dd日 HH:mm");
            }
            else if (date.Year == now.Year)
            {
                // 不是当月的，显示月+日+时分
                return date.ToString("MM月dd日 HH:mm");
            }
            else
            {
                // 不是当年的 显示年+月+日+时分 ，其中年仅显示最后两位
                return date.ToString("yy年MM月dd日 HH:mm");
            }
        }

        /// <summary>
        /// 将日期时间转换为适合在日志或消息中显示的格式。
        /// </summary>
        /// <param name="date">要格式化的日期。</param>
        /// <param name="includeMilliseconds">是否包含毫秒部分，默认为false。</param>
        /// <returns>格式化后的日期时间字符串。</returns>
        public static string ToLogFormat(this DateTime date, bool includeMilliseconds = false)
        {
            return includeMilliseconds
                ? date.ToString("yyyy-MM-dd HH:mm:ss.fff")
                : date.ToString("yyyy-MM-dd HH:mm:ss");
        }

        /// <summary>
        /// 获取日期的开始时间（即当天的 00:00:00.000）。
        /// </summary>
        /// <param name="date">要操作的日期。</param>
        /// <returns>表示当天开始的日期时间。</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static DateTime StartOfDay(this DateTime date)
        {
            return date.Date;
        }

        /// <summary>
        /// 获取日期的结束时间（即当天的 23:59:59.999）。
        /// </summary>
        /// <param name="date">要操作的日期。</param>
        /// <returns>表示当天结束的日期时间。</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static DateTime EndOfDay(this DateTime date)
        {
            return date.Date.AddDays(1).AddMilliseconds(-1);
        }

        /// <summary>
        /// 获取当前周的开始日期（以周一为一周的第一天）。
        /// </summary>
        /// <param name="date">要操作的日期。</param>
        /// <returns>表示本周开始的日期时间。</returns>
        public static DateTime StartOfWeek(this DateTime date)
        {
            int diff = (7 + (date.DayOfWeek - DayOfWeek.Monday)) % 7;
            return date.Date.AddDays(-1 * diff);
        }

        /// <summary>
        /// 获取当前月的开始日期。
        /// </summary>
        /// <param name="date">要操作的日期。</param>
        /// <returns>表示本月开始的日期时间。</returns>
        public static DateTime StartOfMonth(this DateTime date)
        {
            return new DateTime(date.Year, date.Month, 1);
        }

        /// <summary>
        /// 获取当前月的结束日期。
        /// </summary>
        /// <param name="date">要操作的日期。</param>
        /// <returns>表示本月结束的日期时间。</returns>
        public static DateTime EndOfMonth(this DateTime date)
        {
            return date.StartOfMonth().AddMonths(1).AddDays(-1).EndOfDay();
        }

        /// <summary>
        /// 确定给定日期是否是工作日（周一至周五）。
        /// </summary>
        /// <param name="date">要检查的日期。</param>
        /// <returns>如果是工作日则返回true，否则返回false。</returns>
        public static bool IsWeekday(this DateTime date)
        {
            return date.DayOfWeek != DayOfWeek.Saturday && date.DayOfWeek != DayOfWeek.Sunday;
        }

        /// <summary>
        /// 确定给定日期是否是周末（周六或周日）。
        /// </summary>
        /// <param name="date">要检查的日期。</param>
        /// <returns>如果是周末则返回true，否则返回false。</returns>
        public static bool IsWeekend(this DateTime date)
        {
            return !date.IsWeekday();
        }

        /// <summary>
        /// 获取两个日期之间的天数差异，包括开始日期，不包括结束日期。
        /// </summary>
        /// <param name="startDate">开始日期。</param>
        /// <param name="endDate">结束日期。</param>
        /// <returns>两个日期之间的天数。</returns>
        public static int DaysUntil(this DateTime startDate, DateTime endDate)
        {
            return (int)(endDate.Date - startDate.Date).TotalDays;
        }

        /// <summary>
        /// 获取指定日期所在季度的第一天。
        /// </summary>
        /// <param name="date">要操作的日期。</param>
        /// <returns>季度的第一天。</returns>
        public static DateTime StartOfQuarter(this DateTime date)
        {
            int quarterNumber = (date.Month - 1) / 3;
            return new DateTime(date.Year, quarterNumber * 3 + 1, 1);
        }

        /// <summary>
        /// 获取指定日期所在季度的最后一天。
        /// </summary>
        /// <param name="date">要操作的日期。</param>
        /// <returns>季度的最后一天。</returns>
        public static DateTime EndOfQuarter(this DateTime date)
        {
            return date.StartOfQuarter().AddMonths(3).AddDays(-1).EndOfDay();
        }

        /// <summary>
        /// 获取指定日期所在年份的第一天。
        /// </summary>
        /// <param name="date">要操作的日期。</param>
        /// <returns>年份的第一天。</returns>
        public static DateTime StartOfYear(this DateTime date)
        {
            return new DateTime(date.Year, 1, 1);
        }

        /// <summary>
        /// 获取指定日期所在年份的最后一天。
        /// </summary>
        /// <param name="date">要操作的日期。</param>
        /// <returns>年份的最后一天。</returns>
        public static DateTime EndOfYear(this DateTime date)
        {
            return new DateTime(date.Year, 12, 31).EndOfDay();
        }

        /// <summary>
        /// 将DateTime转换为Unix时间戳（秒）。
        /// </summary>
        /// <param name="dateTime">要转换的日期时间。</param>
        /// <returns>Unix时间戳（自1970年1月1日UTC以来的秒数）。</returns>
        /// <example>
        /// <code>
        /// DateTime now = DateTime.Now;
        /// long timestamp = now.ToUnixTimestamp(); // 返回如 1703145600
        /// 
        /// DateTime utcTime = DateTime.UtcNow;
        /// long utcTimestamp = utcTime.ToUnixTimestamp();
        /// 
        /// // API接口常用
        /// var requestData = new { timestamp = DateTime.Now.ToUnixTimestamp() };
        /// </code>
        /// </example>
        /// <remarks>
        /// 如果输入的DateTime为本地时间，会自动转换为UTC时间后计算时间戳。
        /// 适用于API接口、数据库存储、日志记录等需要标准时间戳的场景。
        /// </remarks>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static long ToUnixTimestamp(this DateTime dateTime)
        {
            return ((DateTimeOffset)dateTime).ToUnixTimeSeconds();
        }

        /// <summary>
        /// 将DateTime转换为Unix时间戳（毫秒）。
        /// </summary>
        /// <param name="dateTime">要转换的日期时间。</param>
        /// <returns>Unix时间戳（自1970年1月1日UTC以来的毫秒数）。</returns>
        /// <example>
        /// <code>
        /// DateTime now = DateTime.Now;
        /// long timestampMs = now.ToUnixTimestampMs(); // 返回如 1703145600000
        /// 
        /// // JavaScript时间戳兼容
        /// string jsCode = $"new Date({DateTime.Now.ToUnixTimestampMs()})";
        /// 
        /// // 高精度时间记录
        /// long startTime = DateTime.Now.ToUnixTimestampMs();
        /// // ... 执行操作
        /// long duration = DateTime.Now.ToUnixTimestampMs() - startTime;
        /// </code>
        /// </example>
        /// <remarks>
        /// 毫秒级时间戳常用于JavaScript前端、性能监控、精确计时等场景。
        /// 返回值与JavaScript的Date.getTime()方法兼容。
        /// </remarks>
        public static long ToUnixTimestampMs(this DateTime dateTime)
        {
            return ((DateTimeOffset)dateTime).ToUnixTimeMilliseconds();
        }

        /// <summary>
        /// 将日期时间转换为相对时间的友好显示字符串。
        /// </summary>
        /// <param name="dateTime">要转换的日期时间。</param>
        /// <returns>
        /// 相对时间字符串，如"刚刚"、"3分钟前"、"2小时前"、"昨天"、"3天前"等。
        /// 超过7天的显示具体日期。
        /// </returns>
        /// <example>
        /// <code>
        /// DateTime now = DateTime.Now;
        /// string time1 = now.AddMinutes(-2).ToRelativeTimeString(); // 返回 "2分钟前"
        /// string time2 = now.AddHours(-3).ToRelativeTimeString(); // 返回 "3小时前"
        /// string time3 = now.AddDays(-1).ToRelativeTimeString(); // 返回 "昨天"
        /// string time4 = now.AddDays(-10).ToRelativeTimeString(); // 返回具体日期
        /// 
        /// // 常用于社交媒体、评论、消息等场景
        /// string commentTime = comment.CreatedAt.ToRelativeTimeString();
        /// </code>
        /// </example>
        /// <remarks>
        /// 基于当前时间计算相对时间差，适用于用户界面显示。
        /// 超过一周的时间会显示具体日期格式，避免"100天前"这样的表述。
        /// </remarks>
        public static string ToRelativeTimeString(this DateTime dateTime)
        {
            var timeSpan = DateTime.Now - dateTime;
            var totalSeconds = Math.Abs(timeSpan.TotalSeconds);

            return totalSeconds switch
            {
                < 60 => "刚刚",
                < 3600 => $"{(int)(totalSeconds / 60)}分钟前",
                < 86400 => $"{(int)(totalSeconds / 3600)}小时前",
                < 172800 => "昨天", // 2天内
                < 604800 => $"{(int)(totalSeconds / 86400)}天前", // 7天内
                _ => dateTime.ToString("MM月dd日") // 超过7天显示日期
            };
        }

        /// <summary>
        /// 计算从指定日期时间到现在经过的时间。
        /// </summary>
        /// <param name="fromDate">起始日期时间。</param>
        /// <returns>经过的时间间隔。</returns>
        /// <example>
        /// <code>
        /// DateTime startTime = DateTime.Now.AddHours(-2);
        /// TimeSpan elapsed = startTime.TimeSince(); // 返回约2小时的TimeSpan
        /// 
        /// // 计算账户注册时长
        /// DateTime registerDate = new DateTime(2020, 1, 1);
        /// TimeSpan memberDuration = registerDate.TimeSince();
        /// string memberTime = $"注册{memberDuration.Days}天";
        /// 
        /// // 计算任务执行时间
        /// DateTime taskStart = DateTime.Now;
        /// // ... 执行任务
        /// TimeSpan executionTime = taskStart.TimeSince();
        /// </code>
        /// </example>
        /// <remarks>
        /// 返回的TimeSpan为正值，表示从指定时间到现在的时间差。
        /// 如果指定时间晚于当前时间，返回的TimeSpan为负值。
        /// </remarks>
        public static TimeSpan TimeSince(this DateTime fromDate)
        {
            return DateTime.Now - fromDate;
        }

        /// <summary>
        /// 计算从现在到指定日期时间的剩余时间。
        /// </summary>
        /// <param name="toDate">目标日期时间。</param>
        /// <returns>剩余的时间间隔。</returns>
        /// <example>
        /// <code>
        /// DateTime deadline = DateTime.Now.AddDays(5);
        /// TimeSpan remaining = deadline.TimeUntil(); // 返回约5天的TimeSpan
        /// 
        /// // 活动倒计时
        /// DateTime eventDate = new DateTime(2024, 12, 31, 23, 59, 59);
        /// TimeSpan countdown = eventDate.TimeUntil();
        /// string countdownText = $"距离活动结束还有{countdown.Days}天{countdown.Hours}小时";
        /// 
        /// // 任务截止时间
        /// DateTime taskDeadline = DateTime.Today.AddDays(7);
        /// TimeSpan timeLeft = taskDeadline.TimeUntil();
        /// </code>
        /// </example>
        /// <remarks>
        /// 返回的TimeSpan为正值表示未来时间，负值表示已过期。
        /// 常用于倒计时、截止日期提醒等场景。
        /// </remarks>
        public static TimeSpan TimeUntil(this DateTime toDate)
        {
            return toDate - DateTime.Now;
        }

        /// <summary>
        /// 在指定日期基础上添加工作日（跳过周末）。
        /// </summary>
        /// <param name="date">起始日期。</param>
        /// <param name="businessDays">要添加的工作日天数，可以为负数。</param>
        /// <returns>添加工作日后的日期。</returns>
        /// <example>
        /// <code>
        /// DateTime today = DateTime.Today;
        /// DateTime nextWeek = today.AddBusinessDays(5); // 添加5个工作日
        /// 
        /// // 计算项目交付日期（10个工作日后）
        /// DateTime startDate = new DateTime(2024, 1, 15); // 假设是周一
        /// DateTime deliveryDate = startDate.AddBusinessDays(10);
        /// 
        /// // 倒推开始日期（提前3个工作日开始准备）
        /// DateTime deadline = new DateTime(2024, 2, 1);
        /// DateTime prepareDate = deadline.AddBusinessDays(-3);
        /// </code>
        /// </example>
        /// <remarks>
        /// 此方法只跳过周六和周日，不考虑法定节假日。
        /// 如果起始日期是周末，会自动调整到下一个工作日开始计算。
        /// 适用于项目管理、工期计算、业务日期规划等场景。
        /// </remarks>
        public static DateTime AddBusinessDays(this DateTime date, int businessDays)
        {
            var result = date;
            var direction = businessDays >= 0 ? 1 : -1;
            var remainingDays = Math.Abs(businessDays);

            while (remainingDays > 0)
            {
                result = result.AddDays(direction);
                if (result.IsWeekday())
                {
                    remainingDays--;
                }
            }

            return result;
        }

        /// <summary>
        /// 获取指定日期在当年中是第几周。
        /// </summary>
        /// <param name="date">要计算的日期。</param>
        /// <returns>周数（1-53）。</returns>
        /// <example>
        /// <code>
        /// DateTime newYear = new DateTime(2024, 1, 1);
        /// int week1 = newYear.GetWeekOfYear(); // 返回 1
        /// 
        /// DateTime midYear = new DateTime(2024, 6, 15);
        /// int weekMid = midYear.GetWeekOfYear(); // 返回约24
        /// 
        /// // 生成周报文件名
        /// DateTime reportDate = DateTime.Today;
        /// string fileName = $"WeeklyReport_2024W{reportDate.GetWeekOfYear():D2}.xlsx";
        /// 
        /// // 按周统计数据
        /// var weeklyStats = orders.GroupBy(o => o.OrderDate.GetWeekOfYear());
        /// </code>
        /// </example>
        /// <remarks>
        /// 使用ISO 8601标准计算周数，以周一为一周的开始。
        /// 年初和年末可能出现第53周的情况。
        /// 常用于周报、周统计、项目周期管理等场景。
        /// </remarks>
        public static int GetWeekOfYear(this DateTime date)
        {
            var culture = System.Globalization.CultureInfo.InvariantCulture;
            var calendar = culture.Calendar;
            return calendar.GetWeekOfYear(date, System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
        }

        /// <summary>
        /// 获取指定日期所在的季度。
        /// </summary>
        /// <param name="date">要计算的日期。</param>
        /// <returns>季度数（1-4）。</returns>
        /// <example>
        /// <code>
        /// DateTime spring = new DateTime(2024, 3, 15);
        /// int q1 = spring.GetQuarter(); // 返回 1
        /// 
        /// DateTime summer = new DateTime(2024, 7, 20);
        /// int q3 = summer.GetQuarter(); // 返回 3
        /// 
        /// // 生成季度报告
        /// DateTime reportDate = DateTime.Today;
        /// string quarterReport = $"Q{reportDate.GetQuarter()} {reportDate.Year} Report";
        /// 
        /// // 按季度分组统计
        /// var quarterlySales = sales.GroupBy(s => new { 
        ///     Year = s.SaleDate.Year, 
        ///     Quarter = s.SaleDate.GetQuarter() 
        /// });
        /// </code>
        /// </example>
        /// <remarks>
        /// 季度划分：Q1(1-3月)，Q2(4-6月)，Q3(7-9月)，Q4(10-12月)。
        /// 常用于财务报表、业务统计、项目阶段管理等场景。
        /// </remarks>
        public static int GetQuarter(this DateTime date)
        {
            return (date.Month - 1) / 3 + 1;
        }

        /// <summary>
        /// 判断两个日期是否为同一天。
        /// </summary>
        /// <param name="date1">第一个日期。</param>
        /// <param name="date2">第二个日期。</param>
        /// <returns>如果是同一天则返回true，否则返回false。</returns>
        /// <example>
        /// <code>
        /// DateTime today = DateTime.Today;
        /// DateTime now = DateTime.Now;
        /// bool sameDay = today.IsSameDay(now); // 返回 true
        /// 
        /// DateTime yesterday = DateTime.Today.AddDays(-1);
        /// bool notSameDay = today.IsSameDay(yesterday); // 返回 false
        /// 
        /// // 检查任务是否在同一天创建
        /// bool createdToday = task.CreatedDate.IsSameDay(DateTime.Today);
        /// 
        /// // 过滤今天的订单
        /// var todayOrders = orders.Where(o => o.OrderDate.IsSameDay(DateTime.Today));
        /// </code>
        /// </example>
        /// <remarks>
        /// 只比较日期部分，忽略时间部分。
        /// 常用于数据过滤、日期范围判断、业务逻辑处理等场景。
        /// </remarks>
        public static bool IsSameDay(this DateTime date1, DateTime date2)
        {
            return date1.Date == date2.Date;
        }

        /// <summary>
        /// 判断两个日期是否在同一周内。
        /// </summary>
        /// <param name="date1">第一个日期。</param>
        /// <param name="date2">第二个日期。</param>
        /// <returns>如果在同一周则返回true，否则返回false。</returns>
        /// <example>
        /// <code>
        /// DateTime monday = new DateTime(2024, 1, 1); // 假设是周一
        /// DateTime friday = new DateTime(2024, 1, 5); // 同一周的周五
        /// bool sameWeek = monday.IsSameWeek(friday); // 返回 true
        /// 
        /// DateTime nextMonday = new DateTime(2024, 1, 8);
        /// bool differentWeek = monday.IsSameWeek(nextMonday); // 返回 false
        /// 
        /// // 检查任务是否在本周
        /// bool thisWeekTask = task.DueDate.IsSameWeek(DateTime.Today);
        /// 
        /// // 统计本周数据
        /// var thisWeekData = records.Where(r => r.Date.IsSameWeek(DateTime.Today));
        /// </code>
        /// </example>
        /// <remarks>
        /// 以周一为一周的开始，周日为结束。
        /// 使用ISO 8601标准的周计算方式。
        /// 常用于周统计、周计划、数据分组等场景。
        /// </remarks>
        public static bool IsSameWeek(this DateTime date1, DateTime date2)
        {
            var startOfWeek1 = date1.StartOfWeek();
            var startOfWeek2 = date2.StartOfWeek();
            return startOfWeek1.Date == startOfWeek2.Date;
        }

    }
}
